<?php

defined('IN_MY_PHP') || die(0);
// 初始化本文件单独使用时涉及到的常量
defined('CRLF') || define('CRLF', "\r\n");

/** 是否是本地请求（127.x.x.x）引入的主要目的是方便测试 */
defined('IS_LOCAL_REQUEST') || define('IS_LOCAL_REQUEST', preg_match('/^(127(\\.\\d+){3}|::1)$/', $_SERVER['REMOTE_ADDR']) == 1);

/**
 * A PDOMySQLDriver for MySQL which convert PDOException to E_USER_ERROR
 * @author  netmou(leiyanfo@sina.com).
 * @since   2015/01/07.
 * @link    https://gitee.com/netmou/php-library
 * 1. Do multi-query like the follow one:
 * $stmt = $pdo->query('select * from `A`;select * from `B`');
 * before exec other querys, you shoud iterate all next query-set by using '$stmt->nextRowset()' to avoid the error likes 'Cannot execute queries while other unbuffered queries are active'; A no-meaning loop sample  like the follow code: 
 * do {while($stmt->fetchAll());} while ($stmt->nextRowset());
 * Method '$stmt->closeCursor()' is semantically the same as above, but with limited support in some database.
 * 2. The automatic rollback only happens if you initiate the transaction via PDO::beginTransaction(). If you manually issue a query that begins a transaction PDO has no way of knowing about it and thus cannot roll it back if something bad happens.
 */
class PDOMySQLDriver extends PDO
{

  private $config = array(
    'host'=>MySQL_DB_HOST,
    'dbname'=>MySQL_DB_NAME,
    'port'=>MySQL_DB_PORT,
    'user'=>MySQL_DB_USER,
    'pwd'=>MySQL_DB_PWD,
  );

  //默认返回数据方式
  private $fetchMode = parent::FETCH_ASSOC;

  //表结构缓存
  private $tablePRIMap = array();
  private $tableUNIMap = array();
  private $tableCOLMap = array();
  private $lastQuerySQL = null;
  
  private $options = array(
    PDO::MYSQL_ATTR_INIT_COMMAND =>'SET NAMES UTF8',
    PDO::ATTR_PERSISTENT => false,
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
  );
  /**
   * PDOMySQLDriver 构造函数(host ,dbname, port, user, pwd)
   * dsn-ref http://php.net/manual/en/ref.pdo-mysql.connection.php
   * @param array $config
   * @param array $options
   * @return void
   */
  public function __construct($config = array(), $options = array())
  {
    foreach($config as $key => &$val){
      if(array_key_exists($key, $this->config)){
        $this->config[$key] = $val;
      }
    }
    
    foreach($options as $key => &$val){
      if(array_key_exists($key, $this->options)){
        $this->options[$key] = $val;
      }
    }
    
    $dsn = "mysql:host={$this->config['host']};port={$this->config['port']};dbname={$this->config['dbname']}";
    parent::__construct($dsn, $this->config['user'], $this->config['pwd'], $this->options);
    $this->exec('SET sql_mode=\'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO\'');
    $this->exec('SET SESSION GROUP_CONCAT_MAX_LEN = 4096');
  }

  /**
   * 获取只读的配置项：host
   * @return string
   */
  public function getHost()
  {
    return $this->config['host'];
  }

  /**
   * 获取只读的配置项：dbname
   * @return string
   */
  public function getDBName()
  {
    return $this->config['dbname'];
  }

  /**
   * 获取只读的配置项：port
   * @return string
   */
  public function getPort()
  {
    return $this->config['port'];
  }

  /**
   * 获取最近实际执行的SQL语句(仅用于调试)
   * @return string
   */
  public function getLastQuerySQL()
  {
    return $this->lastQuerySQL;
  }

  /**
   * 格式化字段、表名等标识符
   * @return string
   */
  private function formatKeyWord($keyword, $check = false)
  {
    if ($check && preg_match('/[!\\(\\)><=\\+\\-\\*\\/&\\^%\\|\\s]+/us', trim($keyword))) {

      return $keyword;
    }

    $keyword = str_replace('`', '', $keyword);
    return '`'.str_replace('.', '`.`', $keyword).'`';
  }

  /**
   * 给外部数据进行转义并加边界标识，本函数会将'NULL'字符串转换成'\'NULL\''，NULL转换为'NULL'。
   * @param string  $data
   * @return string
   */
  public function enc($data)
  {
    if($data === null){

      return 'null';
    }

    if(!is_scalar($data)){
      trigger_error("Data must be scalar！", E_USER_ERROR);
    }

    return $this->quote($data);
  }

  /**
   * 设置返回数据的形式
   * @param int   $mode
   * @return void
   */
  public function setFetchMode($mode)
  {
    $this->fetchMode = $mode;
  }

  /**
   * 获取格式化表名并缓存表结构(忽略复合主键).
   *
   * @param string $tableName
   * @return void
   */
  private function tableStruct($tableName)
  {
    $DBName = $this->getDBName();
    $tableNameInfo = explode('.', $tableName);
    if(count($tableNameInfo) == 2){
      $DBName = trim(str_replace('`', '', $tableNameInfo[0]));
      $tableName = trim(str_replace('`', '', $tableNameInfo[1]));
    }
    if('information_schema' == $DBName){
      trigger_error("Can't get struct of information_schema's table !" , E_USER_ERROR);
    }
    $fullTableName = $DBName.'.'.$tableName;

    if($this->tableCOLMap[$fullTableName] != null){
      return $fullTableName;
    }

    $PRIKeyCount = 0;
    $this->tableUNIMap[$fullTableName] = array();
    $this->tableCOLMap[$fullTableName] = array();
    $stmt = parent::query("SELECT `column_name`,`column_default`,`is_nullable`,`data_type`,`column_type`,`column_key`,`column_comment` FROM `information_schema`.`columns` WHERE `table_schema` = '{$DBName}' AND `table_name` = '{$tableName}'");
    while($colInfo = $stmt->fetch()){
      $colName = strtoupper($colInfo['column_name']);
      $this->tableCOLMap[$fullTableName][$colName] = $colInfo;
      if ($colInfo['column_key'] == 'PRI') {
        $this->tablePRIMap[$fullTableName] = $colInfo['column_name'];
        $this->tableUNIMap[$fullTableName][] = $colInfo['column_name'];
        ++$PRIKeyCount;
      }
      if ($colInfo['column_key'] == 'UNI') {
        $this->tableUNIMap[$fullTableName][] = $colInfo['column_name'];
      }
    }
    if(count($this->tableCOLMap[$fullTableName]) == 0){
      trigger_error("Table: {$tableName} don't exist!" , E_USER_ERROR);
    }
    if ($PRIKeyCount != 1) {
      $this->tablePRIMap[$fullTableName] = null;
    }
    
    return $fullTableName;
  }

  /**
   * 执行预查询操作
   * @param string  $prepare
   * @param array   $driver_options
   * @return object PDOStatement
   */
  public function prepare($prepare, $driver_options = array())
  {
    try {
      $this->lastQuerySQL = $prepare;
      return parent::prepare($prepare, $driver_options);
    } catch (PDOException $e) {
      trigger_error(CRLF."-> ".$e->getMessage() . CRLF . "-> On Prepare SQL: " . $prepare, E_USER_ERROR);
    }
  }

  /**
   * 执行查询操作
   * @param string  $query
   * @param int     $fetchMode
   * @return object PDOStatement
   */
  public function query($query, $fetchMode = null){
    
    $this->lastQuerySQL = $query;
    $stmt = parent::query($query);
    $stmt->setFetchMode($fetchMode != null ? $fetchMode : $this->fetchMode);
    return $stmt;
  }

  /**
   * 执行非查询类操作
   * @param string $query
   * @return mixed
   */
  public function exec($query)
  {
    $this->lastQuerySQL = $query;
    return parent::exec($query);
  }

  /**
   * 取得一行结果集中某个字段值
   * @param mixed  $sqlOrStmt
   * @param int    $fetchIndex
   * @return mixed
   */
  public function fetchField($sqlOrStmt, $fetchIndex = 0)
  {
    if (is_string($sqlOrStmt)) {
      $sqlOrStmt = $this->query($sqlOrStmt);
    }
    $rtn = $sqlOrStmt->fetchColumn($fetchIndex);
    $sqlOrStmt->closeCursor();

    return $rtn === false ? null : $rtn;
  }

  /**
   * 取得单行记录.
   * (要警惕在接口中一行数据转换为json后为一个对象{xx:'xx',...},而本函数结果在没有数据时为[])
   * @param mixed  $sqlOrStmt
   * @param int    $fetchMode
   * @return array
   */
  public function fetchRow($sqlOrStmt, $fetchMode = null)
  {
    if (is_string($sqlOrStmt)) {
      $sqlOrStmt = $this->query($sqlOrStmt, $fetchMode);
    }
    $rtn = $sqlOrStmt->fetch();
    $sqlOrStmt->closeCursor();

    return $rtn === false ? array() : $rtn;
  }

  /**
   * 将结果集以键值对的形式储存到数组中.
   * @param mixed  $sqlOrStmt
   * @param int    $fetchMode
   * @return array
   */
  public function fetchData($sqlOrStmt, $fetchMode = null)
  {
    if (is_string($sqlOrStmt)) {
      $sqlOrStmt = $this->query($sqlOrStmt, $fetchMode);
    }
    $rtn = $sqlOrStmt->fetchAll();

    return $rtn === false ? array() : $rtn;
  }

  /**
   * 返回结果集合（返回一列数据并转置成一维数组）.
   * @param mixed  $sqlOrStmt
   * @param int    $fetchMode
   * @return array
   */
  public function fetchSet($sqlOrStmt, $fetchMode = null)
  {
    if (is_string($sqlOrStmt)) {
      $sqlOrStmt = $this->query($sqlOrStmt, $fetchMode);
    }
    if($sqlOrStmt->columnCount() != 1){
      trigger_error("Field count must be only one!", E_USER_ERROR);
    }
    $ret = array();
    while(true){
      $val = $sqlOrStmt->fetchColumn();
      if($val === false){
        break;
      }
      $ret[] = $val;
    }
   
    return $ret;
  }

  /**
   * 过滤数据，剔除数据表中不包含的键值对,并缓存表结构.
   * 本函数适用于过滤向数据库插入更新的数据..
   * @param string $tableName 数据表名
   * @param array  $data  键值对的集合
   * @return array 能够更新数据库的键值对子集
   */
  private function facadeData($tableName, $data)
  {
    $fullTableName = $this->tableStruct($tableName);
    $facadeSet = array();
    foreach ($data as $key => &$val) {
      if (!is_int($key)) {
        $up_key = strtoupper($key);
        // 仅保留数据库中有的信息
        if ($columnInfo = $this->tableCOLMap[$fullTableName][$up_key]) {
          // 处理表单中的空值来匹配非文本字段类型
          if ($val === '' && 0 === preg_match('/char|text|blob/i', $columnInfo['data_type'])) {
            $val = ($columnInfo['is_nullable'] === 'YES')? null : $columnInfo['column_default'];
          }
          // 处理不能为 NULL 的字段
          if ($val === null && $columnInfo['is_nullable'] !== 'YES'){
            $val = $columnInfo['column_default'];
          }
          $facadeSet[$key] = $val;
        }
      }else{
        // update 模式下可以写表达式
        if(strpos($val, '=') === false){
          trigger_error("Update's set clause need assign operator!", E_USER_ERROR);
        }
        $facadeSet[$key] = $val;
      }
    }

    return $facadeSet;
  }

  /**
   * 通过表名获取主键.
   * @param string $tableName
   * @param bool   $strictMode
   * @return mixed
   */
  public function mapTablePri($tableName, $strictMode = true)
  {
    
    $fullTableName = $this->tableStruct($tableName);
    
    $priKey = $this->tablePRIMap[$fullTableName];
    if ($strictMode && $priKey == null) {
      trigger_error("Table ‘{$fullTableName}’ don't have a primary key");
    }

    return $priKey;
  }

  /**
   * 通过映射取得单条记录的某个字段.
   * @param string $tableName
   * @param mixed  $reflex    主键值或数组（键值对应字段，值对应字段值）
   * @param string $field     字段名
   * @return mixed
   */
  public function mapField($tableName, $reflex, $field)
  {
    $condition = array();
    if (is_array($reflex)) {
      $condition = &$reflex;
    } else if(is_scalar($reflex) || $reflex === null) {
      $priKey = $this->mapTablePri($tableName);
      $condition[$priKey] = $reflex;
    }

    if ($field != null) {
      $field = $this->formatKeyWord($field, true);
    }else{
      trigger_error('Must specify a field！', E_USER_ERROR);
    }
    
    $condition = $this->buildAssocCondition($tableName, $condition);
    $tableName = $this->formatKeyWord($tableName);
    return $this->fetchField("SELECT {$field} FROM {$tableName} WHERE {$condition} LIMIT 1 OFFSET 0");
  }

  /**
   * 通过映射取得单条记录.
   * @param string $tableName
   * @param mixed  $reflex    主键值或数组（键值对应字段，值对应字段值）
   * @param mixed  $fieldSet  返回那些字段
   * @return mixed
   */
  public function mapRow($tableName, $reflex, $fieldSet = '*')
  {
    $condition = array();
    if (is_array($reflex)) {
      $condition = &$reflex;
    } else if(is_scalar($reflex) || $reflex === null) {
      $priKey = $this->mapTablePri($tableName);
      $condition[$priKey] = $reflex;
    }
    
    if($fieldSet != '*' && !is_array($fieldSet)){
      // 为防止程序书写错误，强制字段列必须是星号或数组，以和 mapField 加以区别
      trigger_error('Fields must an array or a star!', E_USER_ERROR);
    }
    if (is_array($fieldSet)) {
      foreach ($fieldSet as $key => &$field) {
        $fieldSet[$key] = $this->formatKeyWord($field, true);
      }
      $fieldSet = implode(', ', $fieldSet);
    }
    
    $condition = $this->buildAssocCondition($tableName, $condition);
    $tableName = $this->formatKeyWord($tableName);

    return $this->fetchRow("SELECT {$fieldSet} FROM {$tableName} WHERE {$condition} LIMIT 1 OFFSET 0");
  }

  /**
   * 通过映射取得符合条件的所有数据.
   * @param string $tableName
   * @param mixed  $reflex    主键值或数组（键值对应字段，值对应字段值）
   * @param string $fieldSet     字段名
   * @return mixed
   */
  public function mapData($tableName, $reflex = array(), $fieldSet = '*')
  {

    $condition = array();
    if (is_array($reflex)) {
      $condition = &$reflex;
    } else if(is_scalar($reflex) || $reflex === null) {
      $priKey = $this->mapTablePri($tableName);
      $condition[$priKey] = $reflex;
    }
    
    if($fieldSet != '*' && !is_array($fieldSet)){
      trigger_error("FieldSet must be an array!", E_USER_ERROR);
    }
    if (is_array($fieldSet)) {
      foreach ($fieldSet as $key => &$field) {
        $fieldSet[$key] = $this->formatKeyWord($field, true);
      }
      $fieldSet = implode(', ', $fieldSet);
    }
    
    $condition = $this->buildAssocCondition($tableName, $condition);
    $tableName = $this->formatKeyWord($tableName);

    return $this->fetchData("SELECT {$fieldSet} FROM {$tableName} WHERE {$condition}");
  }

  /**
   * 通过映射取得符合条件的一列数据，并转置成一维数组.
   * @param string $tableName
   * @param mixed  $reflex    主键值或数组（键值对应字段，值对应字段值）
   * @param string $field     字段名
   * @return mixed
   */
  public function mapSet($tableName, $reflex, $valField, $keyField = null){

    $condition = array();
    if (is_array($reflex)) {
      $condition = &$reflex;
    } else if(is_scalar($reflex) || $reflex === null) {
      $priKey = $this->mapTablePri($tableName);
      $condition[$priKey] = $reflex;
    }
    if(!is_scalar($valField)){
      trigger_error("Field must be a string!", E_USER_ERROR);
    }
    
    $valField = $this->formatKeyWord($valField, true);
    
    $condition = $this->buildAssocCondition($tableName, $condition);
    $tableName = $this->formatKeyWord($tableName);
    $retData = array();

    if($keyField == null){
      $stmt = $this->query("SELECT {$valField} AS `col` FROM {$tableName} WHERE {$condition}");
      while($record = $stmt->fetch()){
        $retData[] = $record['col'];
      }
    }else{
      $keyField = $this->formatKeyWord($keyField, true);
      $stmt = $this->query("SELECT {$valField} AS `col_v`, {$keyField} AS `col_k` FROM {$tableName} WHERE {$condition}");
      while($record = $stmt->fetch()){
        $retData[$record['col_k']] = $record['col_v'];
      }
    }
    return $retData;
  }
  
  /**
   * 返回数据库的表信息
   * @param string DBName
   * @return array
   */
  public function mapTables($DBName = null){
    $retData = array();
    $DBName = ($DBName != null)? $DBName : $this->getDBName();
    $stmt = parent::query("SELECT `table_name`, `engine`, `data_length`, `data_free`, `create_time`, `table_rows`, `table_collation`, `table_comment` FROM `information_schema`.`tables` WHERE `table_schema` = '{$DBName}'");
    while ($tableInfo = $stmt->fetch()) {
      $tableName = $tableInfo['table_name'];
      $retData[$tableName] = $tableInfo;
    }

    return $retData;
  }

  /**
   * 返回数据表的列信息
   * 仅获取列名的话可对返回结果进行如下操作：
   * $simpleColumns = $util->arrayColumn($ret, 'column_name');
   * @param string $tableName
   * @return array
   */
  public function mapColumns($tableName)
  {

    $fullTableName = $this->tableStruct($tableName);

    return $this->tableCOLMap[$fullTableName];
  }
  
  /**
   * 求取指定数据表的字段注释
   * @param  string $tableName
   * @return array
   * @deprecated 请使用mapColumn代替
   */
  public function mapComments($tableName)
  {
    $fullTableName = $this->tableStruct($tableName);
    
    $retData = array();
    foreach ($this->tableCOLMap[$fullTableName] as &$info) {
      $key = $info['column_name'];
      $val = $info['column_comment'];
      $retData[$key] = ($val == null)? $key : $val;
    }

    return $retData;
  }

  /**
   * 通过映射主键表删除记录.
   * @param string $tableName
   * @param mixed  $reflex    主键值或数组（键值对应字段，值对应字段值）
   * @return mixed
   */
  public function mapDelete($tableName, $reflex)
  {
    if(func_num_args() > 2){
      trigger_error('Failed, params number wrong!', E_USER_ERROR);
    }
    $condition = array();
    if (is_array($reflex)) {
      $condition = &$reflex;
    } else if(is_scalar($reflex) || $reflex === null) {
      $priKey = $this->mapTablePri($tableName);
      $condition[$priKey] = $reflex;
    }
    
    $condition = $this->buildAssocCondition($tableName, $condition);
    $tableName = $this->formatKeyWord($tableName);

    return $this->exec("DELETE FROM {$tableName} WHERE {$condition}");
  }

  /**
   * 根据条件更新记录，返回受影响的记录数.
   * @param string $tableName
   * @param mixed  $reflex    主键值或数组（键值对应字段，值对应字段值）
   * @param array  $data      更新数据
   * @return int
   */
  public function mapUpdate($tableName, $reflex, $data)
  {
    $condition = array();
    if (is_array($reflex)) {
      $condition = &$reflex;
    } else if(is_scalar($reflex) || $reflex === null) {
      $priKey = $this->mapTablePri($tableName);
      $condition[$priKey] = $reflex;
    }
    
    $data = $this->facadeData($tableName, $data);
    if (count($data) == 0) {
      trigger_error('Failed, no data to update!', E_USER_ERROR);
    }
    
    $setInfo = array();
    foreach ($data as $key => &$val) {
      if(is_int($key)) {
        $setInfo[] = $val;
      }else{
        $setInfo[] = $this->formatKeyWord($key) . '=' . $this->enc($val);
      }
    }
    
    $setClause = implode(', ', $setInfo);
    $whereClause = $this->buildAssocCondition($tableName, $condition);
    $tableName = $this->formatKeyWord($tableName);
    
    return $this->exec("UPDATE {$tableName} SET {$setClause} WHERE {$whereClause}");
  }

  /**
   * 过滤并向数据库插入一条数据，返回新插入数据的自增列.
   * @param string $tableName 数据表名
   * @param array  $data  键值对的集合,通常来自表单
   * @return bool
   */
  public function postData($tableName, $data)
  {
    if(func_num_args() > 2){
      trigger_error('Failed, params number wrong!', E_USER_ERROR);
    }
    $data = $this->facadeData($tableName, $data);
    if (count($data) == 0) {
      trigger_error('Failed, no data to insert！', E_USER_ERROR);
    }
    
    $fieldSet = array();
    $valueSet = array();
    foreach ($data as $key => &$val) {
      $fieldSet[] = $this->formatKeyWord($key);
      $valueSet[] = $this->enc($val);
    }
    
    $fieldStr = implode(', ', $fieldSet);
    $valueStr = implode(', ', $valueSet);
    $tableName = $this->formatKeyWord($tableName);
    $result = $this->exec("INSERT INTO {$tableName} ({$fieldStr}) VALUES ({$valueStr})");
    if($result !== false){

      return $this->lastInsertId();
    }

    return 0;
  }

  /**
   * 一次性向数据库插入多条数据，返回受影响的行数.
   * @param string $tableName 数据表名
   * @param array  $data  待插入二维数据
   * @param array  $fieldSet  字段集合
   * @return bool
   */
  public function postMultiData($tableName, $data)
  {
    if(!is_array($data[0]) || count($data[0]) == 0){
      return 0;
    }
    $fieldSet = array_keys($data[0]);
    $valueData = array();

    foreach($fieldSet as $index => &$field){
      $fieldSet[$index] = $this->formatKeyWord($field);
    }
    $fieldCount = count($fieldSet);
    foreach($data as $record){
      $record = $this->facadeData($tableName, $record);
      foreach ($record as $key => &$val) {
        $record[$key] = $this->enc($val);
      }
      if(count($record) != $fieldCount){
        trigger_error('Failed, data\'s struct is not same!', E_USER_ERROR);
      }
      $valueData[] = '(' . implode(', ', $record) . ')';
    }
    $fieldStr = implode(', ', $fieldSet);
    $valueStr = implode(', ', $valueData);
    $tableName = $this->formatKeyWord($tableName);
    return $this->exec("INSERT INTO {$tableName} ({$fieldStr}) VALUES {$valueStr}");
  }
  

  /**
   * 绑定数据到预处理句柄.
   * @param object $stmt PDOStatement
   * @param array  $paramSet  键值对的集合,通常来自表单
   * @return void
   */
  private function bindAssocArrayValue($stmt, &$paramSet)
  {
    if (is_object($stmt) && ($stmt instanceof PDOStatement)) {
      foreach ($paramSet as $key => &$value) {
        if (is_int($value)) {
          $param = PDO::PARAM_INT;
        } elseif (is_bool($value)) {
          $param = PDO::PARAM_BOOL;
        } elseif (is_null($value)) {
          $param = PDO::PARAM_NULL;
        } elseif (is_string($value) || is_float($value)) {
          $param = PDO::PARAM_STR;
        } else {
          trigger_error('Invalid data type!', E_USER_ERROR);
        }
        $stmt->bindValue(':'.$key, $value, $param);
      }
    }
  }

  /**
   * 构造键值对关联的预查询条件.
   * @param string  $table
   * @param array   $data
   * @return string
   */
  function buildAssocCondition($tableName, &$data)
  {
    $fullTableName = $this->tableStruct($tableName);
    
    $condition = array();
    foreach ($data as $key => &$val) {
      if (!is_int($key)) {
        $up_key = strtoupper($key);
        if ($columnInfo = $this->tableCOLMap[$fullTableName][$up_key]) {
          // SQL COMPARE TO NULL 检查，条件：本地环境+假值+数据库可为NULL
          if(IS_LOCAL_REQUEST && $val == false && $columnInfo['is_nullable'] === 'YES'){
            trigger_error("Local Warning: false value may be compared to NULL width field '{$key}'");
          }
          if(is_array($val)){
            if(count($val) > 0){
              foreach($val as &$item){
                $item = $this->quote($item);
              }
              $condition[] = $this->formatKeyWord($key) . 'in(' . implode(',', $val) .')';
            }else{
              $condition[] = '1 != 1';
            }
            
          }else{
            $condition[] = $this->formatKeyWord($key) . ' = ' . $this->enc($val);
          }
        }else{
          trigger_error("Field: ‘{$key}’ don't exist!", E_USER_ERROR);
        }
      } else {
        // 如果没有指明键值，还要对表达式进行简单检测。(一个没有操作符的值在MySQL中也可能是合法条件)
        if (!preg_match('/[!\\(\\)><=\\+\\-\\*\\/&\\^%\\|\\s]+/us', trim($val))) {
          trigger_error("Expression: ‘{$val}’ may be invalid！", E_USER_ERROR);
        }
        $condition[] = '(' . $val . ')';
      }
    }

    return count($condition) > 0 ? implode(' AND ', $condition) : '1 = 1';
  }

  /**
   * 获取拼接的 INSERT 语句.
   * @param string  $tableName 表名
   * @param array   $data  数据
   * @return string sql
   */
  public function getInsertSql($tableName, $data)
  {
    $values = array();
    $fields = array();
    $data = $this->facadeData($tableName, $data);
    if($data == null){
      trigger_error('No data to insert!', E_USER_ERROR);
    }
    foreach ($data as $key => &$val) {
      $values[] = ($val !== null)? $this->quote($val) : 'NULL';
      $fields[] = $this->formatKeyWord($key);
    }
    
    $tableName = $this->formatKeyWord($tableName);
    $fieldStr = implode(', ', $fields);
    $valueStr = implode(', ', $values);
    
    return "INSERT INTO {$tableName} ({$fieldStr}) VALUES ({$valueStr})";
  }

  /**
   * 获取拼接的 UPDATE 语句.
   * @param string  $table 表名
   * @param array   $data  数据
   * @param mixed   $condition 条件
   * @return string sql
   */
  public function getUpdateSql($tableName, $data, $condition)
  {
    $data = $this->facadeData($tableName, $data);
    if($data == null){
      trigger_error('No data to update!', E_USER_ERROR);
    }
    if($condition == null){
      trigger_error('No condition for update!', E_USER_ERROR);
    }
    
    $setInfo = array();
    $whereInfo = array();
    foreach ($data as $key => &$val) {
      $val = ($val !== null)? $this->quote($val) : 'NULL';
      $key = $this->formatKeyWord($key);
      $setInfo[]= $key . ' = ' . $val;
    }
    foreach ($condition as $key => $val) {
      $key = $this->formatKeyWord($key);
      $val = $this->quote($val);
      $whereInfo[] = $key . ' = ' . $val;
    }
    
    $tableName = $this->formatKeyWord($tableName);
    $setClause = implode(', ', $setInfo);
    $whereClause = implode(' AND ', $whereInfo);
  
    return "UPDATE {$tableName} SET {$setClause} WHERE {$whereClause}";
  }

  /**
   * SELECT查询记录总数
   * @param string $query SQL语句
   * @return int
   */
  public function resultCount($query)
  {
    try {
      // 查询里的 select 子句不能出现(join后)相同的字段名
      return (int) $this->fetchField("SELECT COUNT(*) FROM ({$query}) T_0fjdk3s2a");
    } catch (PDOException $th) {
      $stmt = $this->query($query);
      $data = $stmt->fetchAll(PDO::FETCH_COLUMN, 0);
      $stmt->closeCursor();
      return count($data);
    }
  }

  /**
   * 将带单位的容量转化为字节数
   * @param  integer $size 带单位的容量
   * @return int
   */
  private function paseHumanSize($size){
    $size = str_replace('B','',strtoupper($size));
    if(preg_match('/^([0-9.]+)([KMGTP])$/', $size, $match)){
      $pos = strpos('BKMGTP', $match[2]);
      $size = $match[1] * pow(2, 10 * $pos);
    }
    return $size;
  }
  
  /**
   * 遍历数据库中的表，并对其进行碎片优化
   * @param  integer $maximum 碎片数量
   * @return void
   */
  public function optimize($maximum = '10MB'){
    $DBName = $this->getDBName();
    $maximum = $this->paseHumanSize($maximum);
    $stmt = parent::query("select table_name from information_schema.tables where table_schema='{$DBName}' and data_free > '{$maximum}'");
    while($info = $stmt->fetch()){
      $this->exec("optimize table {$info['table_name']}");
    }
  }

  /**
   * 过滤并向数据库插入一条数据，如果主键或唯一索引冲突则更新数据.
   * 出于业务逻辑安全考虑，对于多个UNIQUE Field（含主键）会报错;
   * 由于innodb_autoinc_lock_mode的配置（默认情况为1，并发行好），
   * INSERT ON DUP UPDATE 语句可能会出现自增列出现不连续现象（空洞）.
   * @param string $tableName 数据表名
   * @param array  $data  键值对的集合,通常来自表单
   * @param array  $updateFields  如果冲突更新哪些字段
   * @return mixed
   */
  public function insertOnDupUpdate($tableName, $data, $updateFields = null){
    $data = $this->facadeData($tableName, $data);
    if (count($data) == 0) {
      trigger_error('Failed ,no data to insert or update！', E_USER_ERROR);
    }
    
    $UNIKeyCount = 0;
    $fullTableName = $this->tablestruct($tableName);
    foreach($this->tableUNIMap[$fullTableName] as $uni_key){
      if(array_key_exists($uni_key, $data)){
        ++$UNIKeyCount;
      }
    }
    if($UNIKeyCount != 1){
      trigger_error('Failed, data must only have an unique Field', E_USER_ERROR);
    }
    
    $fieldSet = array();
    $valueSet = array();
    foreach ($data as $key => &$val) {
      $fieldSet[] = $this->formatKeyWord($key);
      $valueSet[] = ($val === null)? 'NULL' : $this->quote($val);
    }
    
    $updateFields = ($updateFields == null)? array_keys($data) : $updateFields;
    $updateSet = array();
    foreach($updateFields as $key){
      $formated_key = $this->formatKeyWord($key);
      $updateSet[] = $formated_key . ' = VALUES(' . $formated_key . ')';
    }
    
    $fieldStr = implode(', ', $fieldSet);
    $valueStr = implode(', ', $valueSet);
    $updateStr = implode(', ', $updateSet);
    $tableName = $this->formatKeyWord($tableName);
  
    return $this->exec("INSERT INTO {$tableName} ({$fieldStr}) VALUES ({$valueStr}) ON DUPLICATE KEY UPDATE {$updateStr}");
  }

  /**
   * 语法糖，求取指定聚合函数的值.
   * @param string $tableName
   * @param mixed  $reflex 主键值或数组
   * @param string $field? 聚合函数的参数
   * @return mixed
   */
  public function __call($method, $args){
    $prefix = substr($method, 0, 3);
    $callFun = strtoupper(substr($method, 3));
    $funSet = array('MIN', 'MAX', 'AVG', 'COUNT', 'SUM');
    if($prefix == 'map' && in_array($callFun, $funSet) && count($args) < 4){
      $field = (count($args) <= 2)? '*' : $this->formatKeyWord($args[2]);
      $condition = (count($args) == 1)? array('1=1') : $args[1];
      return $this->mapField($args[0], $condition, $callFun.'('.$field.')');
    }
    trigger_error('Failed, method doesn\'t exist in this sql driver !', E_USER_ERROR);
  }
}


// initialize an instance
// try{
//   $pdo= new PDOMySQLDriver();
// }catch(PDOException $e){
//   trigger_error($e->getMessage(), E_USER_ERROR);
// }
